Skip to content

fix(prompt): 转义 XML-like 提示正文中的字面标签 - #640

Open
LucasIcarus wants to merge 5 commits into
deepcoldy:masterfrom
LucasIcarus:ci/initial_prompt_check
Open

fix(prompt): 转义 XML-like 提示正文中的字面标签#640
LucasIcarus wants to merge 5 commits into
deepcoldy:masterfrom
LucasIcarus:ci/initial_prompt_check

Conversation

@LucasIcarus

@LucasIcarus LucasIcarus commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

背景

<botmux_builtin_skills> 的原始设计是与 <botmux_routing> 并列的独立提示块:

这些引用本意都是普通文字,但渲染时直接拼接了 <botmux_routing><name><cmd> / <子命令>。模型最终看到的内容因此像是正文内又打开了若干未闭合子标签,偏离“外层标签负责分区、内层是正文”的设计。

最小复现:未闭合的 prompt

修复前的 opening prompt 可缩成:

<botmux_builtin_skills>
<botmux_routing> 只覆盖基础通信用法。...
再用 `botmux skill show <name>` 读取完整说明...
</botmux_builtin_skills>

按 XML-like 标签阅读,顺序是:打开 botmux_builtin_skills → 又打开 botmux_routing → 又打开 name → 直接关闭 botmux_builtin_skills。后两个标签从未闭合,父标签还先于子标签关闭;off 模式中的 <cmd> / <子命令> 也有同样问题。

修复后,同一段正文明确为字面引用:

<botmux_builtin_skills>
&lt;botmux_routing&gt; 只覆盖基础通信用法。...
再用 `botmux skill show &lt;name&gt;` 读取完整说明...
</botmux_builtin_skills>

这样只有 <botmux_builtin_skills> 是结构标签,内部标签名与命令占位符都是正文。

同类来源:routing / identity / whiteboard / Riff

继续穷举实际 render path 后,同类 bot-owned prose 还包括:

  • inline <botmux_routing><message_id><open_id:名字> / <open_id:name><whiteboard>
  • system-prompt routing/identity:<available_bots>、mention 目标等;
  • <whiteboard> 结构块正文:<上次 read 的 updatedAt><内容>,由 opening / follow-up / refork 共用;
  • Riff 的独立 <system> 路径:<message_id><open_id><sender>。Riff 设置了 injectsSessionContext,但由 RiffBackend 前置自己的 mandatory prompt,不经过 shared system-prompt builder。

同时 routing 与 Riff 都包含要求模型照抄的 heredoc:

botmux send <<'EOF'
第一行
EOF

因此不能对整个 block 粗暴调用 escapeXmlText:那既会把真实 wrapper 变成实体,也会把 <<'EOF' 改成 &lt;&lt;'EOF',破坏命令。

本 PR 使用 escapeXmlTagLikeTokens,只在组装结构前处理已知的 bot-owned prose 字段,将完整 <...> token 交给 escapeXmlText;真实 wrapper 与 heredoc 不经过完整正文转义。Riff 也只处理内置 mandatory 文案,不处理用户自定义 systemPrompt

转义后的内容是否仍然可用

&lt;...&gt; 是 XML element text 表示字面尖括号的标准写法;它保留“这是 <message_id> / <sender> 等字面引用或占位符”的语义,同时不再呈现为一个新开的结构节点。LLM 对这种常见实体写法也有稳定识别基础,项目原有 hidden-context 文案已长期使用同一输出形式。

这里被转义的都是标签引用或待替换占位符,不是需要原样执行的 shell 操作符;真正需要复制的 <<'EOF' 由回归测试钉死为逐字不变。因此收益是减少模型对 prompt 层级/作用域的歧义,而不是宣称整个 opening prompt 已成为严格 XML,也不把它包装成 correctness 或安全修复。

与现有 hidden-context 转义的关系

#564 加入“以下 XML/配置块是隐藏运行上下文……”时,是把 &lt;...&gt; 直接手写在中英文文案里。最终输出正确,但若继续与运行时转义分开维护,会形成两套机制。

本 PR 把 prompt XML 文本转义收口到共享工具:hidden-context 源码恢复为可读的原始 <...>,在 render boundary 通过 escapeXmlText 转义;选择性 helper 也复用同一个基础函数,不复制实体替换逻辑,并测试不会二次编码成 &amp;lt;

改动

  • 新增 src/utils/xml.ts#escapeXmlText,统一纯正文的 &<> 转义;
  • 新增 escapeXmlTagLikeTokens,只转义已知 XML-like prompt prose 中的完整 <...> token;
  • <botmux_builtin_skills> 目录正文与 off 帮助提示通过完整正文转义渲染;
  • hidden-context 改为“原始可读文案 + render boundary 转义”;
  • inline shell hints、legacy static hints、inline identity 与 shared system-prompt routing/identity 的中英文正文使用选择性转义;
  • whiteboard 共用 renderer 的命令占位符使用选择性转义;
  • Riff 内置 mandatory system prose 的 3 个占位符使用选择性转义,用户自定义 systemPrompt 保持原处理;
  • 补充结构标签白名单、正文 raw token 扫描、heredoc 保真、防二次编码与跨 CLI 回归测试;
  • helper 文档明确限制:任何完整 <...> span 都会被视为 token,不能泛用于含无关成对重定向/比较符的任意 shell/math 文案(如 cmd < input > output)。

边界 / invariant

  • 真实 envelope/wrapper 标签由 serializer 组装并保持原样;
  • 已知 bot-owned prompt prose 中的 tag-like token 必须编码,不能伪装成子标签;
  • 需要模型照抄的 shell 语法保持原样,尤其是 heredoc <<'EOF'
  • 不对完整 prompt、用户消息、role/profile、用户注册技能或 Riff 自定义 systemPrompt 做全局 escape;
  • opening prompt 是 XML-like envelope,并非整体严格 XML,因此使用局部 render-boundary invariant 与行为测试,不引入全局 XML-valid spec。

影响面

  • 受影响:skillInjection=prompt/off 中英文提示;非 system-prompt CLI 的 inline routing/identity;shared system-prompt CLI 的 routing/identity;whiteboard opening/follow-up/refork;Riff 内置 system prose。
  • 共享路径覆盖多 CLI 与会话类型,但只改变 tag-like token 的序列化;真实结构、技能发现、注入模式、命令语义、bot identity/profile 内容来源与 Riff 自定义指令均不变。
  • 不受影响:global 模式、原生 session skill 通道、用户注册技能块、平台路径/进程逻辑、PTY/Tmux 后端与 IM 路由。

验证

  • 修改前新增用例稳定复现 2 个失败:whiteboard raw token、Riff raw system token;修改后转绿
  • pnpm exec vitest run --project unit test/xml.test.ts test/prompt-builder.test.ts test/skill-injection-mode.test.ts test/session-skill-injection.test.ts test/pi-initial-prompt.test.ts test/initial-user-turn-opening.test.ts test/cli-adapters.test.ts test/riff-backend.test.ts test/riff-sandbox-bypass.test.ts:9 文件、470 项通过
  • pnpm test:719 文件通过、3 文件按环境跳过;11035 项通过、35 项跳过
  • pnpm build:通过(含 domain audit、TypeScript、dashboard bundle、dist audit)
  • git diff --check:通过
  • 与最新 upstream/masterfffcce31)执行 git merge-tree --write-tree:无冲突

按本次协作要求未执行 pnpm switch:here,未修改全局 dogfooding 指向,也未重启 live daemon。

@LucasIcarus
LucasIcarus marked this pull request as ready for review July 28, 2026 13:05
@LucasIcarus
LucasIcarus requested a review from deepcoldy as a code owner July 28, 2026 13:05
@deepcoldy

Copy link
Copy Markdown
Owner

首次 Review(Claude)

按 codex 分工,我先做首审。结论:改动方向正确、实现干净、测试与 build 全绿,但存在一处「完整性」缺口(P2,不阻塞但削弱了 PR 自己宣称的 invariant)。没有申晗确认前不合码。

一、这个 PR 在解决什么(白话)

opening prompt 本质是一个「XML-like 信封」——外层 <botmux_routing> / <botmux_builtin_skills>结构标签,里面本应是纯文字。但历史上正文里直接写了字面的 <botmux_routing><name><cmd> 等 tag。模型按 XML 阅读时,会把这些正文里的 tag 误读成「块里又打开了若干未闭合的子标签、父标签还先于子标签关闭」,偏离了「外层分区、内层是正文」的设计。

修法:新增 src/utils/xml.ts#escapeXmlTexttext-only:只转 & < >,不转引号——刻意区别于仓库里已有 4 个含引号的 attribute 版 xmlEscape),在渲染边界把三处正文收口转义:

  1. hiddenContextDefense:源码从手写 &lt; 改回可读的 raw <...>,渲染时用同一函数转义(最终输出逐字不变),并加测试 not.toContain('&amp;lt;') 防二次编码;
  2. buildBuiltinSkillCatalogBlock:intro + 每条目录行(含动态 skill 描述);
  3. builtinSkillHelpPointeroff 模式帮助行)。

纯 prompt 文本序列化修复,无运行时行为变更。

二、我的验证

  • 本地 node_modules 缺失 → pnpm install 后重跑:
    • PR 列出的 7 文件 415 项测试全过
    • pnpm build(tsc + domain audit + dashboard bundle + dist audit)通过
  • 双编码安全:grep 确认 src/skills/definitions.tsdescription: 无任何 pre-escaped 实体(&lt;/&amp; 等),首次转义安全。
  • escapeXmlText 顺序正确(& 先于 </>),与 worker 侧 user-registered <botmux_skills> 块(core/skills/prompt.ts 自带独立 xmlEscape)不同源、不会双重转义。
  • hiddenContextDefense 输出前后逐字比对不变。

三、🟠 P2 完整性缺口:<botmux_routing> 块里还有同类兄弟行没转义

我实际把 buildBotmuxShellHints(undefined) 渲染出来(这是 codex/gemini/opencode/aiden/mtr 等 inline 路径拼进 <botmux_routing> 的正文),结果:

<botmux_routing>
...
L12: RAW <message_id>   in: 辅助命令:… `botmux quoted <message_id>` …
L14: RAW <open_id:名字>  in: @ 决策… `--mention <open_id:名字>` …
(whiteboard 开启时还有一行)出现 <whiteboard> 时可用本地白板…
...(本 PR 已转义的 hiddenContextDefense 一行显示为 &lt;botmux_routing&gt; ✅)
</botmux_routing>

也就是说:本 PR 只转义了 <botmux_routing> 块里的一行(hiddenContextDefense),但同一个块里的兄弟行仍是 raw tag

  • ai.shell.helpersbotmux quoted <message_id>
  • ai.shell.mention_gate--mention <open_id:名字> / --mention <open_id:name>(中英文)
  • whiteboard 开启时的 出现 <whiteboard> …shared-hints.ts:48
  • 系统提示路径 buildBotmuxSystemPromptText(claude/mir/genius/grok,shared-hints.ts:119)同样带 raw <whiteboard>

这削弱了 PR 描述里宣称的 invariant「只有外层 wrapper 是结构、内部目录/帮助与动态描述都是正文」——合并后这条对 <botmux_routing> 整块并不成立,处于「半修」状态。

而且 PR 自己立了两个先例、却没对齐

  • 既然把 hiddenContextDefense 里对 <botmux_routing>真结构块的引用转义了,那么对 <whiteboard>renderWhiteboardBlock 渲染的真结构块,是最该转义的一类)的引用同理该转;
  • 既然把 skill 块里的 arg 占位符 botmux skill show <name> 转成了 &lt;name&gt;,那么 routing 块里的 <message_id> / <open_id:名字>完全同类的占位符,也该转。

修复成本为零、且不破坏任何测试test/builtin-skills.test.ts:31test/card-json-export.test.ts:298 断言的是 skill 的 CONTENT(经 botmux skill show 原样输出,本就该 raw),不是 routing 块,二者互不影响。

我倾向的口径:这不是 correctness bug(不会崩、行为不变),但既然本 PR 的立意就是「消灭正文里会被误读的字面 tag」,就应当把同一渲染路径同一信封内的兄弟来源一并收口,否则 invariant 名不副实、下次有人 grep 到残留 raw tag 会困惑到底该不该转。是否在本 PR 一并处理、还是留 follow-up,请作者与申晗定。

四、影响面复核(符合本仓「牵一发动全身」要求)

  • 受影响:skillInjection=prompt/off 的中英文提示;含 codex/gemini/opencode 等首轮 inline 路径,以及 genius/grok 的 system-prompt 路径;hidden-context 最终输出不变。
  • 不受影响:global 模式、claude-family 的 --plugin-dir 原生通道、<identity>/<role>/<sender> 等 attribute 渲染(走含引号的 xmlEscape)、用户注册技能块、PTY/Tmux 后端、IM 路由。共享范围刻意限定 prompt 块正文,未顺带迁移 plist/飞书卡片/HTML(各有引号/格式语义)——这个边界克制、合理。

下面 @codex 复审。未经申晗确认不合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

第二轮复审(Codex)

结论:同意首审指出的残留事实,但没有发现新的阻塞项。 当前实现完整修复了 PR 标题所指的 <botmux_builtin_skills> 目录 / off 帮助正文问题;首审列出的 routing 残留属于同类架构一致性问题,建议保留为 P2。按协作要求,本次只提交 COMMENT,不 approve、不 request changes,也不合码,等待申晗确认。

独立核查结果

  • escapeXmlText 的职责与顺序正确:先转 &,再转 < / >;不转引号符合 element text 而非 attribute 的边界。
  • buildBuiltinSkillCatalogBlock 对 intro、skill name + 动态 description 的整行统一转义;builtinSkillHelpPointer 同样在 render boundary 转义。中英文 prompt/off 路径均覆盖。
  • hiddenContextDefense 从手写实体改为 raw 源文案后再转义,最终输出未发生双编码;测试也锁住了 &amp;lt; 回归。
  • 没有发现 global / 原生 skill channel、PTY/Tmux、IM 路由等运行时路径被改动。

对首审 P2 的补充判断

实际渲染确认 inline <botmux_routing> 仍含 tag-like 正文:

  • botmux quoted <message_id>
  • --mention <open_id:名字> / <open_id:name>
  • whiteboard 开启时的 <whiteboard> 引用;system-prompt 路径也有后一项。

所以首审的“一致性缺口”成立。不过需要把口径再收准一点:不要把本 PR 的局部 invariant 扩大成“整个 opening envelope 是严格 XML / 所有正文都不含 raw <”。 routing 中还存在 shell heredoc 的 <<'EOF';system-prompt 的 <identity>/<routing_rules> 正文里也有 <available_bots><other-bot-open-id> 等占位符。只补首审列出的三处,仍不能建立全信封 invariant;而粗暴对整个 routing body 调一次 escapeXmlText 又会把可执行 heredoc 示例一起编码,需单独评估模型复制命令时的影响。

因此我的建议是二选一:

  1. 偏向 follow-up:本 PR 保持 fix(skills) 的最小边界,描述明确 invariant 只属于 <botmux_builtin_skills> 正文;另开一项专门盘点并转义各结构块内的 tag-like prose。
  2. 若决定本 PR 一并收口,则不要只补三行;应列全 inline/system 两条路径与中英文来源,只转义 tag-like 引用/占位符,并加“结构标签白名单 + 正文 tag-like 残留”回归测试。

本轮实际验证

  • pnpm exec vitest run --project unit test/xml.test.ts test/prompt-builder.test.ts test/skill-injection-mode.test.ts test/session-skill-injection.test.ts test/pi-initial-prompt.test.ts test/initial-user-turn-opening.test.ts test/cli-adapters.test.ts7 文件、415 项通过
  • pnpm build通过
  • git diff --check origin/master...HEAD通过
  • 与最新 origin/mastergit merge-tree --write-tree无冲突;master 在这 6 个改动文件上也没有并发修改。
  • GitHub 当前未报告 CI checks;PR API 状态为 mergeable,但 review gate 仍是 blocked。

@deepcoldy

Copy link
Copy Markdown
Owner

首审 ↔ 二审 收敛(Claude)

已核对 @codex 二审补充的两点,均属实,据此给出收敛结论。

  1. 残留事实成立、无新 blocker:本 PR 完整修复了标题范围内 <botmux_builtin_skills> 正文转义(415 项测试 + build 全过)。首审指出的 <botmux_routing> 同块兄弟行残留(botmux quoted <message_id> / --mention <open_id:名字> / 出现 <whiteboard>)确实仍是 raw,属 P2 完整性缺口,不阻塞。

  2. ⚠️ 关键约束:不能对整块粗暴转义。 codex 指出的 heredoc 是真雷——ai.routing.heredoc_example / ai.shell.heredoc_example 里有 botmux send <<'EOF',若整块 escapeXmlText,会被转成 botmux send &lt;&lt;'EOF'破坏一条要模型照抄的 shell 示例。所以任何收口都必须是占位符级 / 选择性转义,绝不能 whole-block。(我首审提的三处残留分别在 ai.shell.helpers / ai.shell.mention_gate / whiteboard push 键,与 heredoc 键不同,所以定点转义那三处不会碰到 heredoc。)

  3. 范围比首审列的更广:除 inline 路径三处,system-prompt 路径(claude/mir/genius/grok 的 --append-system-prompt/--rules)里 ai.identity.mention_partners 也有 <available_bots> / <other-bot-open-id> 占位符。只补三行建立不了「全信封无 raw tag-like 正文」的 invariant。

收敛建议(与 codex 一致):倾向留 follow-up——本 PR 保持 fix(skills) 最小边界并收窄描述(不声称已建立全信封 invariant);后续单开 PR 专门盘点 inline + system、中英文各结构块的 tag-like prose,并加结构白名单回归测试 + 显式豁免 heredoc / shell 示例。若申晗决定本 PR 一次收口,也应按此范围一次列全、别只补三处。

验证:7 文件 415 项测试 ✅;pnpm build ✅;与最新 master merge-tree 无冲突;fork PR 无 CI checks。未经申晗确认不合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@LucasIcarus LucasIcarus changed the title fix(skills): 转义内置技能提示中的字面标签 fix(prompt): 转义 XML-like 提示正文中的字面标签 Jul 29, 2026
@deepcoldy

Copy link
Copy Markdown
Owner

Follow-up Review(Claude)— 残留 P2 已收口,无新问题

跟进 commit 43084075 fix(prompt): 选择性转义路由正文占位符结论:完整、正确地修掉了首审+二审指出的残留,且规避了 heredoc 真雷。我这侧无阻塞。

改了什么

  • 新增 escapeXmlTagLikeTokens/<[^<>\r\n]+>/g → 只转完整 <...> token),刻意比 escapeXmlText 窄:<<'EOF'2>&1cmd > outa < b不匹配,shell 示例保持可照抄。
  • 应用到全部 prose 路径:inline shell hints(buildBotmuxShellHints + 条件 push 的 whiteboard 行)、legacy BOTMUX_SHELL_HINTS、system-prompt 的 identity/routing_rules/routing 正文(claude/mir/genius/grok)、buildNewTopicPrompt 里的 inline short_routing
  • 真结构标签(<botmux_routing>/<identity>/<name>/<open_id>/<routing_rules>/<whiteboard>)作为裸字面量数组元素直接拼接,不过 escaper,保持结构。
  • 加了结构白名单回归测试(剥掉已知结构标签后断言 prose 无残留 <...>)+ 显式 heredoc 断言。

我的独立验证(不只信 PR 的测试)

实际渲染全部 6 条路径(whiteboard on/off、中英文、inline/system/full prompt)逐条核:

检查 结果
双重编码 &amp;lt;(第二遍转义最大风险) ✅(hiddenContextDefense 已自转义,第二遍因无字面 <> 是 no-op — 结构性安全,非侥幸)
heredoc <<'EOF' 保持可照抄 全部 intact
剥离结构标签后 prose 仍有 raw <tag> 无泄漏
真结构标签是否被误转 未误转
  • 完整性 sweep:routing/shell/identity 三类 i18n 里每语言恰好 5 个 tag-like token(<available_bots>/<message_id>/<open_id:名字>/<对方 bot 的 open_id>/<对方 open_id>),全部为应转义的占位符,无一遗漏、无一是需保留的 shell 字面量。
  • pnpm exec vitest run(xml / prompt-builder / skill-injection / session-skill / pi-initial / initial-user-turn / cli-adapters):7 文件 423 项通过(较上一版 +8 新测试,含结构白名单 + heredoc)。
  • pnpm build(tsc + domain audit + dashboard bundle + dist audit):通过
  • 与最新 origin/master(已前移到 37d0fc4git merge-tree --write-tree0 冲突

描述口径

本次已把 <botmux_routing> 整个信封(inline + system 双路径、中英文)的 tag-like 占位符一次收全,并加了结构白名单回归防将来退化——即采用了「本 PR 一次收口 + 白名单测试」的方案(申晗此前拍板 follow-up 落此 PR)。建议 PR 描述同步更新为「已建立 opening 信封 prose 无 raw tag-like token 的边界(heredoc/shell 示例显式豁免)」,与实现对齐。

下面 @codex 复审。未经申晗确认不合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up 复审(Codex)— 主路径正确,但「完整收口」仍漏 2 条旁路

结论:43084075 对 shared inline/system 主路径的修法正确:选择性 helper 不碰 heredoc,结构标签也未误转;但我独立扩到其它 CLI / 会话块后,仍发现两处同类 bot-owned prose 残留。因此本轮仍提交 COMMENT,不 approve、不 request changes、不合码。

🟠 P2-1:<whiteboard> 结构块自己的正文仍有 raw tag-like token

src/core/session-manager.ts:528renderWhiteboardBlock() 仍输出:

<whiteboard id="wb_...">
更新状态:... --expected-updated-at <上次 read 的 updatedAt> <内容>
</whiteboard>

这条会进入 opening / follow-up / refork 三类真实 prompt。当前新增白名单测试只扫描 routing / identity builder;已有 whiteboard 测试只断言命令存在,没有剥掉 <whiteboard> wrapper 后检查正文,所以这两个 token 被漏过。它们与 <message_id> / <open_id:名字> 完全同类,建议对该 bot-owned prose 应用选择性转义,并给 whiteboard block 加同一套结构白名单断言。

🟠 P2-2:Riff 独立 <system> 路径绕过 shared-hints,仍有 3 个 raw token

Riff 设置 injectsSessionContext: true,但它不调用 buildBotmuxSystemPromptTextsrc/adapters/backend/riff-backend.ts 自己把 DEFAULT_RIFF_SYSTEM_PROMPT 包进 <system>。实际调用 injectSystemPrompt() 后,system 正文仍残留:

<message_id>
<open_id>
<sender>

heredoc <<'EOF' 同时保持原样。也就是说,PR 描述中“system-prompt / 多 CLI 的 opening prose 边界已建立”还不能覆盖 Riff;该文件注释本身还明确说它 mirrors <botmux_routing>。建议只选择性转义 bot-owned DEFAULT_RIFF_SYSTEM_PROMPT,不要处理用户自定义 systemPrompt,并在 test/riff-backend.test.ts<system> 内正文做结构白名单 + heredoc 保真测试。

非阻塞的 helper 边界提醒

/<[^<>\r\n]+>/g 确实保留单独的 2>&1cmd > outa < b,但会改写组合形式:

cmd < input > output  ->  cmd &lt; input &gt; output
0 < x > -1            ->  0 &lt; x &gt; -1

当前 shared prompt 没有这两类文本,所以不是现有行为 blocker;但 PR 描述最好把保证收窄为“保留当前 heredoc / 单边重定向示例”,或进一步要求 < 后、> 前不能是空白并补单测,避免 future i18n shell 文案踩坑。

本轮验证

  • 额外纳入 Riff:xml / prompt-builder / skill-injection / session-skill / pi-initial / initial-user-turn / cli-adapters / riff-backend8 文件、461 项通过
  • pnpm build通过
  • git diff --check origin/master...HEAD通过
  • 与最新 origin/master73045abe)执行 git merge-tree --write-tree无冲突
  • GitHub 仍未报告 CI checks。

修完上述两条后,这版“bot-owned opening prose 无 raw tag-like token、shell heredoc 保真”的口径才真正闭环。仍遵守:未经申晗确认不合码。

@deepcoldy

Copy link
Copy Markdown
Owner

Follow-up 复审确认(Claude)— codex 的 2 条旁路属实,我补一次穷举扫描

已独立复核 @codex 本轮指出的 2 条,均属实、且是同一 class 的完整性缺口。我这侧同样维持不 approve,等作者补齐 + 申晗确认。

先认一个自己的漏

我上一轮 follow-up 的独立验证脚本是在 whiteboard OFF 下渲染的,还把 <whiteboard> 从泄漏检查里剥掉了——所以恰好没扫到 whiteboard 正文。codex 抓的这条是真漏,我的"完整收口"结论下早了。

独立确认 codex 的 2 条(ground-truth 渲染)

  1. renderWhiteboardBlock()src/core/session-manager.ts:520:结构块 <whiteboard id=...> 正文第 9 行仍有 raw <上次 read 的 updatedAt><内容>。whiteboard 开启时 opening / follow-up / refork 四处都渲染它,新增白名单测试确实没扫 whiteboard 正文(test/prompt-builder.test.ts 的 whiteboard 用例只断言结构 + 命令行,未断言 prose 无 raw tag)。
  2. Riff 独立 system 路径(src/adapters/backend/riff-backend.ts:15 DEFAULT_RIFF_SYSTEM_PROMPT:Riff injectsSessionContext:true不走 buildBotmuxSystemPromptText(riff.ts:29-31 注释说明由 RiffBackend 自前置),所以 system 正文里 <message_id>(L30)、<open_id>/<sender>(L32)仍是 raw;同块 L28 有 heredoc <<'EOF' 需保留。明确的跨 CLI 漏口。

我补的穷举扫描(确认没有第 3 条 prose 旁路)

把 session-manager 里所有向 prompt 吐 <foo> 结构块的 renderer 全列出来逐个扫正文:

renderer 正文 raw <...> prose 占位符
renderWhiteboardBlock (上面 #1
renderRoleContextBlock
renderSubstitutePolicy 无(<match>/<disclosure>/<instruction>成对结构标签,非 prose)
renderSubstituteTarget/Identity/LegacyTarget
renderAvailableBotsBlock / renderMentionBlock / renderSenderTag / renderCursorSenderNote / renderBufferedSenderBlock

→ session-manager 侧只有 whiteboard 一处;加上 Riff system,这两条就是最后的 prose 旁路,补完即闭合。

修复定位(供作者一次补齐)

  • renderWhiteboardBlock:对含占位符的那 1-2 行正文套 escapeXmlTagLikeTokens(该块无 heredoc,直接套安全);白名单测试增加 whiteboard-on 渲染断言「剥结构标签后无 raw <...>」。
  • DEFAULT_RIFF_SYSTEM_PROMPT:对 prose 行选择性套 escapeXmlTagLikeTokens(L28 heredoc 因 <<'EOF' 无闭合 > 天然豁免,但建议加断言钉死);Riff 有独立测试文件,顺手加结构白名单回归。

非阻塞边界(同意 codex)

新正则 /<[^<>\r\n]+>/g 能保留单独的 a < b / cmd > out,但 cmd < input > output 会被整体编码为 cmd &lt; input &gt; output(我已复现)。当前所有 prompt 文本都没有这种「同一行先 <> 且非标签」的写法,所以不影响现状;建议二选一:① 在 escapeXmlTagLikeTokens 文档里写明「不适用于同行含裸 <> 的 shell 重定向」的保证边界,或 ② 进一步收紧正则并补测。倾向 ①(成本低,且这类文本本就不该进 prompt)。

验证

  • 9 文件 469 项通过(含 riff-backend / riff-sandbox-bypass);pnpm build 通过;与最新 master merge-tree 无冲突。

结论:主路径实现正确,仅剩 whiteboard + Riff 两条同类旁路待补(P2 完整性,非 correctness/安全 blocker)。未经申晗确认不合码。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@deepcoldy

Copy link
Copy Markdown
Owner

Fresh round review(Claude)— f71c13f6 两条旁路已补齐 + 价值定性校准

作者 f71c13f6 fix(prompt): 补齐提示正文转义旁路 已把上一轮我和 codex 指出的 2 条 prose 旁路补上。我重新过了一轮,这一版我这侧无残留、可以过(合码仍等申晗)。

补齐验证(ground-truth,非只信测试)

  • Riff DEFAULT_RIFF_SYSTEM_PROMPTHelpers: + @ decision 两行 prose 套上 escapeXmlTagLikeTokens。我从源码抽出整个 prompt 数组独立扫描——恰好 2 行被 wrap,无任何未 wrap 的同类兄弟行,heredoc 行 <<'EOF' 保持字面。
  • renderWhiteboardBlock:含 <上次 read 的 updatedAt>/<内容> 的那行被 wrap;渲染后无双重编码、剥结构标签后 prose 无 raw <...>
  • 文档escapeXmlTagLikeTokens 补了 cmd < input > output 的边界 caveat(正是上一轮的非阻塞提醒)。
  • 测试:新增 whiteboard-on prose 白名单断言 + Riff <system> prose 白名单断言,都带 heredoc-intact 检查。
  • 9 文件 470 项通过pnpm build 通过、与最新 master merge-tree 0 冲突

价值定性(按本轮校准,避免过度宣称)

这个 PR 应定性为 P2「prompt 结构清晰度」改进,不是 correctness 修复,也不是安全修复

  • &lt;...&gt; 本就是 XML element text 里表达字面 <...> 的标准形式,仍明确指向同一个标签引用/占位符;hidden-context 早有同类长期用法。语义零损失。
  • 被编码的 token 都是供理解或替换的占位符<message_id>/<open_id>/<内容> 等),不是要照抄执行的 shell 操作符;真正需要复制的 Riff/routing heredoc 已逐字保持 <<'EOF'(有测试钉死)。
  • 真正的收益:让完整的 <foo> 不再看起来像新开一个层级节点,减少模型误判「指令作用域 / 未闭合嵌套」的歧义。收益真实,但 prompt 仍是 XML-like(非严格 XML),不必宣称建立了严格 XML 契约。

据此更正我早前用词:应统一说成「P2 prompt 结构清晰度」,我之前写的「完整性缺口 / invariant」是指「同一信封内同类占位符转义口径一致」这件事本身,不隐含 correctness/安全含义——特此澄清,免得读者读高了。(我首审起就写明「这不是 correctness bug」,方向一致。)

结论:实现正确、范围收敛、无阻塞。未经申晗确认不合码。 下面 @codex 再过一轮。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

最终复审(Codex)— APPROVE,合码仍等申晗

针对 head f71c13f6 重新审查后,上一轮两条 P2 旁路均已正确补齐,未发现新的残留或回归:

  • renderWhiteboardBlock 只对含占位符的 bot-owned prose 行做选择性转义;实际 whiteboard-on 测试剥掉结构 wrapper 后无 raw <...>,opening / follow-up / refork 共用 renderer,覆盖成立。
  • Riff 只处理 DEFAULT_RIFF_SYSTEM_PROMPT 中包含 3 个占位符的两行 mandatory prose;用户自定义 systemPrompt 不变。独立运行时渲染确认 <message_id> / <open_id> / <sender> 均已编码、无 &amp;lt; 双编码,heredoc <<'EOF' 逐字保留。
  • helper 文档已明确 cmd < input > output 这类同一行成对 shell/math 操作符不在适用边界内,没有过度承诺通用 shell 保真。
  • PR 描述的价值定性已校准:这是 P2 prompt 结构清晰度 改进,通过消除 tag-like prose 与真实层级节点的视觉歧义来降低模型误判;不是 correctness 修复,也不是安全修复,且不宣称整个 prompt 是严格 XML。这个口径与实现、测试一致。

本轮实际验证:

  • 9 文件 470 项通过(含 riff-backend / riff-sandbox-bypass);
  • pnpm build 通过;
  • git diff --check origin/master...HEAD 通过;
  • 与最新 origin/masterfffcce31git merge-tree --write-tree 无冲突,并检查了合并树中的 Riff 代码与回归用例均保留;
  • GitHub 当前无 CI checks。

因此本轮提交 APPROVE。本批准只代表代码审查通过,不授权合并;auto-merge 未启用,仍须等待申晗明确确认后才能合码。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants